package easy.springioc.context.support;

import com.alibaba.fastjson.JSON;
import easy.springioc.beans.BeansException;
import easy.springioc.beans.config.BeanDefinition;
import easy.springioc.beans.config.RefBeanDefinition;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {

    /**
     * xml配置文件的路径
     */
    private String xmlPath;


    public ClassPathXmlApplicationContext(String xmlPath)  throws IOException{
        this.xmlPath = xmlPath;
        /**
         * 1.读取bean的XML配置文件

         2.使用beanId查找bean配置，并获取配置文件中class的地址

         3.使用Java反射技术实例化对象

         4.获取属性配置，使用反射技术进行赋值
         */
        loadBeanDefinitions();
        // 4.实例化Spring中的依赖注入
        doAutowired();
    }

    @Override
    protected void loadBeanDefinitions() throws IOException{
        // 1.读取xml配置文件
        System.out.println("读取xml配置文件: xmlPath: "+xmlPath);

        // 1.1创建xml解析器
        SAXReader saxReader = new SAXReader();
        System.out.println("1.1创建xml解析器");

        // 1.2读取xml配置文件
        Document read = null;
        System.out.println("1.2读取xml配置文件");
        try {
            read = saxReader.read(this.getClass().getClassLoader().getResourceAsStream(xmlPath));
        } catch (DocumentException e) {
            e.printStackTrace();
        }

        // 1.3获取xml配置文件的根节点对象（<beans></beans>）
        System.out.println("1.3获取xml配置文件的根节点对象（<beans></beans>）");
        Element rootElement = read.getRootElement();

        //1.4获取根节点中所有的子节点对象，也就是所有bean对象(<bean></bean>)
        System.out.println("1.4获取根节点中所有的子节点对象，也就是所有bean对象(<bean></bean>)");
        List<Element> beanElements = rootElement.elements();

        Object obj = null;

        // <bean id="world1" class="easy.springioc.entity.HelloWorld">
        for (Element beanElement : beanElements) {

            // 2.使用beanId查找bean配置，并获取配置文件中class的地址(为了与参数beanId区分开，我们命名为beanElementId)
            System.out.println("2.使用beanId查找bean配置，并获取配置文件中class的地址(为了与参数beanId区分开，我们命名为beanElementId)");

            //2.1使用id查找bean配置
            String beanElementId = beanElement.attributeValue("id");
            // 2.2获取bean对应的Class地址
            String beanClazz = beanElement.attributeValue("class");

            // 3.使用反射实例化对象

            // 3.1获取Class对象
            System.out.println(" 3.1获取Class对象");
            Class<?> clazz = null;
            BeanDefinition beanDefinition = null;
            try {
                clazz = Class.forName(beanClazz);
                // 3.2实例化对象
                System.out.println(" 3.2实例化对象");
                obj = clazz.newInstance();

                // 4.获取属性配置，使用反射技术进行赋值
                System.out.println(" 4.获取属性配置，使用反射技术进行赋值");

                // 4.1获取所有属性
                System.out.println("  4.1获取所有属性");

                List<Element> fieldElements = beanElement.elements();

                beanDefinition =  new BeanDefinition(beanElementId, obj);
                System.out.println("BeanDefinition: "+ JSON.toJSONString(beanDefinition));

                for (Element fieldElement : fieldElements) {
                    // <property name="name" value="侏罗纪"></property>
                    String name = fieldElement.attributeValue("name");
                    String value = fieldElement.attributeValue("value");
                    String ref = fieldElement.attributeValue("ref");
                    if (Objects.nonNull(value)){
                        // 4.2使用反射api为私有属性赋值
                        System.out.println("  4.2使用反射api为私有属性赋值");
                        Field declaredField = clazz.getDeclaredField(name);
                        //忽略访问权限修饰符的安全检查，又称为暴力反射
                        declaredField.setAccessible(true);
                        declaredField.set(obj, value);
                    }else if (Objects.nonNull(ref)){
                        // <property name="person" ref ="person1"></property>
                        List<RefBeanDefinition> dependencyBeanList = dependencyBeanMap.get(beanDefinition);
                        if (dependencyBeanList == null){
                            dependencyBeanList = new ArrayList<>();
                        }
                        dependencyBeanList.add(new RefBeanDefinition(name,ref));

                        dependencyBeanMap.put(beanDefinition, dependencyBeanList);
                        //  <bean id="person1" class="easy.springioc.entity.PersonEntity" >
                    }
                }
                System.out.println("BeanDefinition: "+ JSON.toJSONString(beanDefinition)+", 依赖的半成品bean："+ Arrays.asList(dependencyBeanMap.get(beanDefinition)));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e){
                e.printStackTrace();
            } catch (SecurityException e){
                e.printStackTrace();
            } catch (IllegalArgumentException e){
                e.printStackTrace();
            } catch (IllegalAccessException e){
                e.printStackTrace();
            } catch (InstantiationException e){
                e.printStackTrace();
            }

            beanIdDefinitionMap.put(beanElementId, beanDefinition);
            beanNameDefinitionMap.put(beanDefinition.getBeanName(), beanDefinition);
            beanClassDefinitionMap.put(clazz, beanDefinition);
        }
    }


    private void doAutowired() {
        System.out.println("实例化Spring中的依赖注入");
        if (dependencyBeanMap.size() == 0){
            return;
        }
        Iterator<Map.Entry<BeanDefinition, List<RefBeanDefinition>>> dependencyBeanMapIt = dependencyBeanMap.entrySet().iterator();
        while (dependencyBeanMapIt.hasNext()){
            Map.Entry<BeanDefinition, List<RefBeanDefinition>> e = dependencyBeanMapIt.next();
            BeanDefinition beanDefinition = e.getKey();
            // 检查依赖对象是否定义以及实例化
            List<RefBeanDefinition> dependencyBeanList = e.getValue();
            for (RefBeanDefinition dependencyBean: dependencyBeanList) {
                String dependencyBeanBeanId = dependencyBean.getBeanId();
                BeanDefinition refBeanDefinition = beanIdDefinitionMap.get(dependencyBeanBeanId);
                if (refBeanDefinition == null){
                    throw  new BeansException("refBeanIdId: "+dependencyBeanBeanId+" Undefined");
                }
            }
            // 通过反射注入依赖对象实例
            Object beanInstance = beanDefinition.getBeanInstance();
            Field[] declaredFields = beanInstance.getClass().getDeclaredFields();
            List<Field> declaredFieldList = Arrays.asList(declaredFields);
            for (RefBeanDefinition dependencyBean: dependencyBeanList){
                String dependencyBeanBeanId = dependencyBean.getBeanId();
                BeanDefinition refBeanDefinition = beanIdDefinitionMap.get(dependencyBeanBeanId);
                Object refBeanDefinitionBeanInstance = refBeanDefinition.getBeanInstance();

                String filedName = dependencyBean.getName();
                declaredFieldList.stream().filter(declaredField-> Objects.equals(declaredField.getName() , filedName)).forEach(declaredField->{
                    try {
                        declaredField.setAccessible(true);
                        declaredField.set(beanInstance, refBeanDefinitionBeanInstance);
                    } catch (IllegalAccessException e1) {
                        e1.printStackTrace();
                    }
                });
            }
        }
    }


    @Override
    public Object getBeanById(String id) throws IOException {
        return beanIdDefinitionMap.get(id).getBeanInstance();
    }

    @Override
    public Object getBeanByName(String beanName) throws IOException {
        return beanNameDefinitionMap.get(beanName).getBeanInstance();
    }

    @Override
    public Object getBeansOfType(Class requiredType) throws IOException {
        return beanClassDefinitionMap.get(requiredType).getBeanInstance();
    }



}
